#pragma once

#include <gtl/atom/detail.h>
#include <gtl/string/str.h>
#include <gtl/string/trim.h>

#include <sstream>

namespace gtl
{

namespace atom
{

namespace itunes_atoms
{

//////////////////////////////////////////////////////////////////////////
//iTunesDB Database Layout
//MHBD (Database)
//|
//|-MHSD (Dual Sub-Databases)
//| |
//| |-MHLT (Tunes List)
//| | |
//| | |-MHIT (Tune Item)
//| | | |
//| | | |-MHOD (Description Object)
//| | | |-MHOD
//| | | | ...
//| | |
//| | |-MHIT
//| | | |
//| | | |-MHOD
//| | | |-MHOD
//| | | | ...
//| | |
//| | |-...
//|
//|
//|-MHSD
//| |
//| |-MHLP (Playlists List)
//| | |
//| | |-MHYP (Playlist)
//| | | |
//| | | |-MHOD
//| | | |-MHIP (Playlist Item)
//| | | | ...
//| | |
//| | |-MHYP
//| | | |
//| | | |-MHOD
//| | | |-MHIP
//| | | | ...
//| | |
//| | |-...
//////////////////////////////////////////////////////////////////////////

class mhbd : private gtl::atom::details<mhbd, 'mhbd', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		gtl::tstring version_str;
		(*patom)(_T("version")) = cast_to_str(version_number, itunes_versions::versions, 21);

		return true;
	}

public:
	reg_into_factory();
	template<class Values>
	static gtl::tstring cast_to_str(uint32 value, Values* values, uint32 values_len)
	{
		for(uint32 i = 0; i < 21; ++i)
		{
			if(values[i].value == value)
				return values[i].description;
		}

#ifdef UNICODE
		std::wstringstream sstrm;
#else
		std::stringstream sstrm;
#endif
		sstrm << "0x" << std::hex << std::uppercase << value;
		return sstrm.str();
	}

private:
	struct itunes_version_info
	{
		uint32		value; // version
		gtl::tchar*	description;
	};

	template<class T>
	struct itunes_versions_t
	{
		static itunes_version_info versions[];
	};

	typedef itunes_versions_t<void> itunes_versions;

private:
	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 unk1;	// always seems to be 1
	uint32 version_number;
	uint32 number_of_children;
	uint8  id[8];	// appears to a 64 bit id value for this database. 
					// Not checked by the iPod, as far as I can tell.
	uint16 unk2;	// always seems to be 2
	uint8  unk3[8];	// Observed in dbversion 0x11 and later. 
					// It was thought that this field is used to store some sort of 
					// starting point to generate the item's dbid, but this idea was thrown away.
	uint16 unk4;	// Observed in dbversion 0x19 and later, and must be set to 0x01 for the new 
					// iPod Nano 3G (video) and iPod Classics. The obscure hash at offset 88
					// needs to be set as well.
	uint8  unk5[20];// Observed in dbversion 0x19 and later for the new iPod Nano 3G (video) 
					// and iPod Classics. Meaning unknown so far.
	uint16 language;// Observed in dbversion 0x13. 
					// It looks like this is a language id (langauge of the iTunes interface). 
					// For example for English(United States) this field has values 0x65 and 0x6E 
					// which is 'en'. The size of the filed might be bigger to distinguish 
					// different 'flavors' of a language.
	uint8  library_persistent_id[8];// Observed in dbversion 0x14. This is a 64-bit Persistent ID
									// for this iPod Library. This matches the value of "Library 
									// Persistent ID" seen in hex form (as a 16-char hex string) 
									// in the drag object XML when dragging a song from an iPod 
									// in iTunes.
	uint8  obscure_hash[20];		// Observed in dbversion 0x19 for iPod Nano 3G (video) and 
									// iPod Classics.
};

template<class T>
mhbd::itunes_version_info mhbd::itunes_versions_t<T>::versions[] = 
{
	0x09, _T("iTunes 4.2"), 
	0x0a, _T("iTunes 4.5"), 
	0x0b, _T("iTunes 4.7"), 
	0x0c, _T("iTunes 4.71/4.8"),
	0x0d, _T("iTunes 4.9"),
	0x0e, _T("iTunes 5"),
	0x0f, _T("iTunes 6"),
	0x10, _T("iTunes 6.0.1(?)"),
	0x11, _T("iTunes 6.0.2-6.0.4"),
	0x12, _T("iTunes 6.0.5."),
	0x13, _T("iTunes 7.0"),
	0x14, _T("iTunes 7.1"), 
	0x15, _T("iTunes 7.2"), 
	0x16, _T("0x16"),
	0x17, _T("iTunes 7.3.0"),
	0x18, _T("iTunes 7.3.1-7.3.2."),
	0x19, _T("iTunes 7.4"),
	0x1A, _T("iTunes 7.5"),
	0x1B, _T("iTunes 7.6.0"), 
	0x1C, _T("iTunes 7.6.1."),
	0x1D, _T("iTunes 7.6.2.9")
};

class mhsd : private gtl::atom::details<mhsd, 'mhsd', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		gtl::tstring type_str;
		(*patom)(_T("version")) = mhbd::cast_to_str(child_type, children_type::children, 5);

		return true;
	}

private:
	reg_into_factory();
	struct child_type_info
	{
		long		value; // type
		gtl::tchar* description;
	};

	template<class T>
	struct children_type_t
	{
		static child_type_info children[];
	};

	typedef children_type_t<void> children_type;

private:
	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 child_type;
};

template<class T>
mhsd::child_type_info mhsd::children_type_t<T>::children[] = 
{
	0x1, _T("Track list"), 
	0x2, _T("Playlist List"), 
	0x3, _T("Podcast List "), 
	0x4, _T("Album List"),
	0x5, _T("New Playlist List with Smart Playlists"),
};

class mhlt : private gtl::atom::details<mhlt, 'mhlt', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("songs")) = gtl::tstr(number_of_songs);
		return true;
	}

private:
	reg_into_factory();

	uint32 identify;
	uint32 header_length;
	uint32 number_of_songs;
	// rest of header is zero padded
};

class mhit : private gtl::atom::details<mhit, 'mhit', little_endian>
{
public:
	mhit()
	{
		visible = 1;
	}

public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("visible")) = gtl::tstr(visible);
		(*patom)(_T("track_id")) = gtl::tstr(track_id);
		(*patom)(_T("media_type")) = mhbd::cast_to_str(media_type, media_types::types, 9);
		(*patom)(_T("number_of_mhods")) = gtl::tstr(number_of_mhods);
		(*patom)(_T("playcount")) = gtl::tstr(play_count);
		return true;
	}

private:

private:
	reg_into_factory();
	struct media_type_info
	{
		uint32		value; // type
		gtl::tchar*	description;
	};

	template<class T>
	struct media_types_t
	{
		static media_type_info types[];
	};

	typedef media_types_t<void> media_types;

private:
	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 number_of_mhods;	// number of strings (mhods) that are children of this mhit
	uint32 track_id;		// unique ID for a track (referenced in playlists)
	uint32 visible;			// If this value is 1, the song is visible on the iPod.
							// All other values cause the file to be hidden. 
							// Was previously known as unk1.
	uint32 filetype;
	uint8  type1;
	uint8  type2;
	uint8  compilation_flag;
	uint8  stars_rating;
	uint32 last_modified_time;
	uint32 size;
	uint32 length;
	uint32 track_number;	// number of strings (mhods) that are children of this mhit
	uint32 total_tracks;
	uint32 year;
	uint32 bitrate;
	uint32 sample_rate;
	uint32 volume;
	uint32 start_time;
	uint32 stop_time;
	uint32 soundcheck;
	uint32 play_count;
	uint32 play_count_2;
	uint32 last_played_time;
	uint32 disc_number;
	uint32 total_discs;
	uint32 userid;
	uint32 date_added;
	uint32 bookmark_time;
	uint8  dbid[8];
	uint8  checked;
	uint8  application_rating;
	uint16 BPM;
	uint16 artwork_count;
	uint16 unk9;
	uint32 artwork_size;
	uint32 unk11;
	uint32 sample_rate_2;
	uint32 date_released;
	uint16 unk14;
	uint16 explicit_flag;
	uint32 unk15;
	uint32 unk16;
	uint32 skip_count;
	uint32 last_skipped;
	uint8  has_artwork;
	uint8  skip_when_shuffling;
	uint8  remember_playback_position;
	uint8  flag4;
	uint8  dbid2[8];
	uint8  lyrics_flag;
	uint8  movie_file_flag;		// if 0x1, it is a movie file. Otherwise, it is an audio file.
	uint8  played_mark;
	uint8  unk17;
	uint32 unk21;
	uint32 pregap;
	uint8  sample_count[8];
	uint32 unk25;
	uint32 postgap;
	uint32 unk27;
	uint32 media_type;
	uint32 season_number;
	uint32 episode_number;
	uint32 unk31;
	uint32 unk32;
	uint32 unk33;
	uint32 unk34;
	uint32 unk35;
	uint32 unk36;
	uint32 unk37;
	uint32 gapless_data;
	uint32 unk38;
	uint16 gapless_track_flag;
	uint16 gapless_album_flag;
	uint8  unk39[20];
	uint32 unk40;
	uint32 unk41;
	uint32 unk42;
	uint32 unk43;
	uint16 unk44;
	uint16 album_id;
	uint32 mhii_link;
	// The rest of the mhit is zero padded.
};

template<class T>
mhit::media_type_info mhit::media_types_t<T>::types[] = 
{
	0x00, _T("Audio/Video"),
	0x01, _T("Audio"),
	0x02, _T("Video"),
	0x04, _T("Podcast"), 
	0x06, _T("Video Podcast"),
	0x08, _T("Audiobook"),
	0x20, _T("Music Video"),
	0x40, _T("TV Show (shows up ONLY in TV Shows)"),
	0x60, _T("TV Show (shows up in the Music lists as well)")
};		  

class mhod : private gtl::atom::details<mhod, 'mhod', little_endian>
{
public:
	mhod()
	{
		description = NULL;
	}

	~mhod()
	{
		if(description != NULL)
			delete[] description;
	}

public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("type")) = gtl::tstr(type);

		const unsigned long fix_len = fixed_length == 0 ? 40 : fixed_length;

		uint32 length = (unsigned long)patom->length() - fix_len;
		description = new uint16[length / 2 + 1];
		memset(description, 0, (length / 2 + 1) * sizeof(uint16));
		patom->read(fix_len, length, (uchar*)description);

		gtl::str description_str = gtl::string::trans((const wchar_t*)description);
		description_str.rtrim();
		gtl::tstr value_str = description_str.trans();;

		switch(type)
		{
		case 1:	// title
			(*patom)(_T("title")) = value_str;
			break;

		case 2:	// path
			(*patom)(_T("path")) = value_str;
			break;

		case 3:	// Album
			(*patom)(_T("album")) = value_str;
			break;

		case 4:	// artist
			(*patom)(_T("artist")) = value_str;
			break;

		case 5:
			(*patom)(_T("genre")) = value_str;
			break;

		case 52:
			(*patom)(_T("index_type")) = gtl::tstr(index_type);
			(*patom)(_T("number_of_songs")) = gtl::tstr(number_of_songs);
			break;

		default:
			break;
		}

		return true;
	}

private:
	reg_into_factory();

	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 type;
	uint32 unk1;
	uint32 unk2;
	union {
		uint32 position;	// for type less than 15.
							// In type 100 mhod's in playlists, this is where the 
							// playlist order info is. It does not seem to be 
							// significant in string mhod's (except for 
							// location - see following notes). 
							// Note: This field does not exist in ArtworkDB string 
							// dohms. This was observed to be 2 for inversed endian
							// ordered iTunesDBs for mobile phones with UTF8 strings 
							// and 1 for standard iPod iTunesDBs with UTF16 strings. 
							// Note: If you leave this set to zero on the type 
							// 2 (location) string MHOD of a Song (mhit) record, 
							// the track will show on the menu, but will not play. 
		uint32 smart_list_rules_identifier; // for type 51.
											// "SLst" (note that this is the point at which bytes 
											// are no longer little-endian in the mhod. 
											// it switches to big-endian at this point) 
		uint32 index_type;	// for type 52, what this index is sorted on
							// (see list above)
	};
	union {
		uint32 string_length;	// Length of the string, in bytes. If the string 
								// is UTF-16, each char takes two bytes. The string 
								// in the iTunesDB is not NULL-terminated either. 
								// Keep this in mind. Be careful with very long 
								// strings - it has been observed that strings longer 
								// than ~512 characters will cause the iPod to 
								// continously reboot when it attempts to read the database. 
		uint32 number_of_songs;	// for type 52, number of entries. 
								// Always the same as the number of entries in the 
								// playlist, which is the same as the number of songs 
								// on the iPod. 
	};
	// for type 52, null padding  40  lots of padding  
	union {
		uint32 unknown;
		uint32 number_of_rules;	// for type 51. number of rules.
	};
	union {
		uint32 unk4;
		uint32 rules_operator;	// for type 51. 
								// 0 = AND (as in "Match All"), 
								// 1 = OR (as in "Match Any") 
	};
	uint16* description;		// the length of [string_length] string.
								// for type 52, index entries  4 * count  
								// The index entries themselves. 
								// This is an index into the mhit list, in order, 
								// starting from 0 for the first mhit. 
};

class mhlp : private gtl::atom::details<mhlp, 'mhlp', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("playlists")) = gtl::tstr(number_of_playlists);
		return true;
	}

private:
	reg_into_factory();

	uint32 identify;
	uint32 header_length;
	uint32 number_of_playlists;
	// rest of header is zero padded
};

class mhyp : private gtl::atom::details<mhyp, 'mhyp', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("data_object_count")) = gtl::tstr(data_object_child_count);
		(*patom)(_T("playlist_items")) = gtl::tstr(playlist_item_count);
		//(*patom)(_T("persistent_playlist_id")) = gtl::tstr(persistent_playlist_id);
		return true;
	}

private:
	reg_into_factory();

	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 data_object_child_count;	// number of Data Objects in the List 
	uint32 playlist_item_count;		// number of Playlist Items in the List 
	uint8  is_master_playlist_flag;
	uint8  unk1[3];
	uint32 timestamp;
	uint8  persistent_playlist_id[8];
	uint32 unk3;
	uint16 string_mhod_count;
	uint8  podcast_flag;
	uint8  group_flag;
	uint32 list_sort_order;
};

class mhip : private gtl::atom::details<mhip, 'mhip', little_endian>
{
public:
	bool parse(atom* patom)
	{
		if(!base_type::parse(patom))
			return false;

		(*patom)(_T("children")) = gtl::tstr(data_object_child_count);
		(*patom)(_T("track_id")) = gtl::tstr(track_id);
		return true;
	}

private:
	reg_into_factory();

	uint32 identify;
	uint32 header_length;
	uint32 total_length;
	uint32 data_object_child_count;
	uint16 podcast_grouping_flag;	// Formerly unk1. 0x0 = normal file. 0x100 = Podcast Group. 
									// Podcast Groups will be followed by a single child, 
									// an MHOD type 1 string, which specifies the name of the 
									// Podcast Group. They will also have a 0 (zero) for the Track ID. 
									// This field used to be some kind of correlation ID or something, 
									// but this may have been a bug. In any case, the old way breaks 
									// iPods now, and this should be set to zero on all normal songs. 
	uint8  unk4;		// 0 or 1 in iTunes 7.2. 
	uint8  unk5;		// 0 or 8 in iTunes 7.2. 
	uint32 group_id;	// Formerly unk2. A unique ID for the track.
						// It appears it is made sure that this ID does not correspond to 
						// any real track ID. Doesn't seem to correlate to anything, 
						// but other bits reference it. See Podcast Grouping Reference below.
	uint32 track_id;	// the ID number of the track in the track list. See Track Item for more info
	uint32 timestamp;	// some kind of time stamp, possibly time the song was added to the playlist
	uint32 podcast_grouping_reference;	// Formerly unk3. This is the parent group that this podcast 
										// should be listed under. It should be zero the rest of the time.
										// rest of header is zero padded
};

} // end of namespace itunes_atoms

class itunes_details
{
public:
	bool parse(atom* atom_ptr)
	{
		idetails* detail = factory::instance()->create(atom_ptr->type());
		if(detail != NULL)
			return detail->parse(atom_ptr);
		return true;
	}
};

} // end of namespace atom

} // end of namespace gtl